/*
     This file is part of libmicrohttpd
     Copyright (C) 2019 Christian Grothoff (and other contributing authors)

     This library is free software; you can redistribute it and/or
     modify it under the terms of the GNU Lesser General Public
     License as published by the Free Software Foundation; either
     version 2.1 of the License, or (at your option) any later version.

     This library is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     Lesser General Public License for more details.

     You should have received a copy of the GNU Lesser General Public
     License along with this library; if not, write to the Free Software
     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
/**
 * @file http_chunked_compression.c
 * @brief example for how to compress a chunked HTTP response
 * @author Silvio Clecio (silvioprog)
 */

#include "platform.h"
#include <zlib.h>
#include <microhttpd.h>

#define CHUNK 16384

struct Holder {
    FILE *file;
    z_stream stream;
    void *buf;
};

static int
compress_buf (z_stream *strm, const void *src, size_t src_size, size_t *offset, void **dest, size_t *dest_size,
              void *tmp)
{
  unsigned int have;
  int ret;
  int flush;
  void *tmp_dest;
  *dest = NULL;
  *dest_size = 0;
  do
    {
      if (src_size > CHUNK)
        {
          strm->avail_in = CHUNK;
          src_size -= CHUNK;
          flush = Z_NO_FLUSH;
        }
      else
        {
          strm->avail_in = (uInt) src_size;
          flush = Z_SYNC_FLUSH;
        }
      *offset += strm->avail_in;
      strm->next_in = (Bytef *) src;
      do
        {
          strm->avail_out = CHUNK;
          strm->next_out = tmp;
          ret = deflate (strm, flush);
          have = CHUNK - strm->avail_out;
          *dest_size += have;
          tmp_dest = realloc (*dest, *dest_size);
          if (NULL == tmp_dest)
            {
              free (*dest);
              *dest = NULL;
              return MHD_NO;
            }
          *dest = tmp_dest;
          memcpy ((*dest) + ((*dest_size) - have), tmp, have);
        }
      while (0 == strm->avail_out);
    }
  while (flush != Z_SYNC_FLUSH);
  return (Z_OK == ret) ? MHD_YES : MHD_NO;
}

static ssize_t
read_cb (void *cls, uint64_t pos, char *mem, size_t size)
{
  struct Holder *holder = cls;
  void *src;
  void *buf;
  src = malloc (size);
  if (NULL == src)
    return MHD_CONTENT_READER_END_WITH_ERROR;
  size = fread (src, 1, size, holder->file);
  if ((ssize_t) size < 0)
    {
      size = MHD_CONTENT_READER_END_WITH_ERROR;
      goto done;
    }
  if (0 == size)
    {
      size = MHD_CONTENT_READER_END_OF_STREAM;
      goto done;
    }
  if (MHD_YES != compress_buf (&holder->stream, src, size, &pos, &buf, &size, holder->buf))
    size = MHD_CONTENT_READER_END_WITH_ERROR;
  else
    {
      memcpy (mem, buf, size);
    }
  free (buf); /* Buf may be set even on error return. */
done:
  free (src);
  return size;
}

static void
free_cb (void *cls)
{
  struct Holder *holder = cls;
  fclose (holder->file);
  deflateEnd (&holder->stream);
  free (holder->buf);
  free (holder);
}

static int
ahc_echo (void *cls, struct MHD_Connection *con, const char *url, const char *method, const char *version,
          const char *upload_data, size_t *upload_size, void **ptr)
{
  struct Holder *holder;
  struct MHD_Response *res;
  int ret;
  (void) cls;
  (void) url;
  (void) method;
  (void) version;
  (void) upload_data;
  (void) upload_size;
  if (NULL == *ptr)
    {
      *ptr = (void *) 1;
      return MHD_YES;
    }
  *ptr = NULL;
  holder = calloc (1, sizeof (struct Holder));
  if (!holder)
    return MHD_NO;
  holder->file = fopen (__FILE__, "rb");
  if (NULL == holder->file)
    goto file_error;
  ret = deflateInit(&holder->stream, Z_BEST_COMPRESSION);
  if (ret != Z_OK)
    goto stream_error;
  holder->buf = malloc (CHUNK);
  if (NULL == holder->buf)
    goto buf_error;
  res = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, 1024, &read_cb, holder, &free_cb);
  if (NULL == res)
    goto error;
  ret = MHD_add_response_header (res, MHD_HTTP_HEADER_CONTENT_ENCODING, "deflate");
  if (MHD_YES != ret)
    goto res_error;
  ret = MHD_add_response_header (res, MHD_HTTP_HEADER_CONTENT_TYPE, "text/x-c");
  if (MHD_YES != ret)
    goto res_error;
  ret = MHD_queue_response (con, MHD_HTTP_OK, res);
res_error:
  MHD_destroy_response (res);
  return ret;
error:
  free (holder->buf);
buf_error:
  deflateEnd (&holder->stream);
stream_error:
  fclose (holder->file);
file_error:
  free (holder);
  return MHD_NO;
}

int
main (int argc, char *const *argv)
{
  struct MHD_Daemon *d;
  unsigned int port;
  if ((argc != 2) ||
      (1 != sscanf (argv[1], "%u", &port)) ||
      (UINT16_MAX < port))
    {
      fprintf (stderr, "%s PORT\n", argv[0]);
      return 1;
    }
  d = MHD_start_daemon (MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD, (uint16_t) port, NULL, NULL,
                        &ahc_echo, NULL,
                        MHD_OPTION_END);
  if (NULL == d)
    return 1;
  if (0 == port)
    MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT, &port);
  fprintf (stdout, "HTTP server running at http://localhost:%u\n\nPress ENTER to stop the server ...\n", port);
  (void) getc (stdin);
  MHD_stop_daemon (d);
  return 0;
}
